2

阳光正好Electron今天出发

本文阅读需要花费一点时间,如果时间紧迫者可以阅读开头以及结尾总结。thanks
来咿呀快活呀


首先问一点问题

  1. 什么是Electron
  2. Electron是用来干什么的,解决些什么
  3. 写个todo看看得怎么玩?
  4. 怎么实现桌面程序,概念意义,底层的技术大概是什么?

第一步 如何安装

首先我们的选择可以在save在开发依赖上
npm install electron --save-dev
也可以全局安装electron
npm install electron -g

这里有一段文档翻译有意思的话

Electron 可以让你使用纯 JavaScript 调用丰富的原生(操作系统) APIs 来创造桌面应用。 你可以把它看作一个专注于桌面应用的 Node. js 的变体,而不是 Web 服务器。

这不意味着 Electron 是绑定了 (GUI) 库的 JavaScript。 相反,Electron 使用 web 页面作为它的 GUI,所以你能把它看作成一个被 JavaScript 控制的,精简版的 Chromium 浏览器。

awesome

第二步 了解基本概念

Electron 分主进程与渲染进程

主进程运行package.json 的main脚本被成为主进程。运行在主进程中的脚本将以创建 web 页面的方式显示一个 GUI。
(这让我想起了。以前做python 跟java时候gui主线程的感觉。。。)

Electron的web页面运行在一个成为渲染进程的进程中。 并且在页面与操作系统中获得一些低级别的交互

主进程 渲染进程 区别

主进程使用BrowserWindow实例创建页面

每个 BrowserWindow实例都在自己的渲染进程里运行页面。

当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终止。

渲染进程相互独立,主进程管理。

渲染进程只需要关心它们自己的页面。

网页内是不能进行GUI操作的。

需要对应的渲染进程与主进程之间通信。

通信方法

1、使用HTML5 API 例如storage API、localstorage、sessionstorage或IndexedDB

2、IPC通信( ipcRenderer 和 ipcMain)

3、RPC通信(remote模块)

下面介绍实现一个todolist

首先编码开始。

它管控文件与package.json类似。

主线程由main定义的入口文件创建

以下是不完整代码。大概的。

需要完整代码可以查看github地址。

todolist

项目目录大概为
todo
-- addWindow.html
-- main.js
-- package.json
-- window.html

入口文件是我们的main.js,它是一个应用中的中心调度。

// 可以向使用nodejs时一样引入模块
// 这个也就是所谓的主进程,看起来就像是nodejs脚本
const electron = require('electron');
const url = require('url');
const path = require('path');


const {app,BrowserWindow,Menu,ipcMain} = electron;

//设置环境
process.env.NODE_ENV = 'production';

let mainWindow;
let addWindow;
//listen for app to be ready

app.on('ready',function(){
    //1. 创建一个新的窗口
    mainWindow = new BrowserWindow({});
    //加载 html进窗口
    mainWindow.loadURL(url.format({
        pathname: path.join(__dirname,'window.html'),
        protocol: 'file:', //协议
        slashes:true
    }));
    //file:/dirname/mainWindow.html

    //6. 大窗口关闭整个程序关闭
    mainWindow.on('close',function(){
        app.quit();
    })

    //2 .设置菜单
    const mainMenu = Menu.buildFromTemplate(mainMenuTemplate);
    Menu.setApplicationMenu(mainMenu);

})


//handle create add window 

function createAddWindow(){
    //5. 新建窗口
    addWindow = new BrowserWindow({
        width: 300,
        height: 200,
        title: 'Add TODO list item'
    });
    //load html into window
    addWindow.loadURL(url.format({
        pathname: path.join(__dirname,'addWindow.html'),
        protocol: 'file:', //协议
        slashes:true
    }));
    // 垃圾回收
    addWindow.on('close',function(){
        addWindow = null;
    })
}

//7. 捕获子窗口ipc item:add
// IPC通信
ipcMain.on('item:add',function(e,item){
    console.log('item');
    mainWindow.webContents.send('item:add',item);
    addWindow.close();
});

const mainMenuTemplate = [{
    label: 'File',
    submenu: [//3. 设置子菜单
        {
            label: 'Add Item',
            click(){
                //4. 新建窗口
                createAddWindow();
            }

        },
        {
            label: 'Clear Items',
            click(){
                mainWindow.webContents.send('item:clear');
            }
        },
        {
            label: 'Quit',
            accelerator: process.platform =='darwin'? 'Commond+Q':'Ctrl+Q',//快捷键做os版本区分
            click(){
                app.quit();
            }
        }
    ]
}];


// if mac , add empty object to menu
if(process.platform =='darwin'){
    mainMenuTemplate.unshift({});
}


// 区分开发环境
if(process.env.NODE_ENV !== 'production'){
    // add devtools
    mainMenuTemplate.push({
        label:'Developer tools',
        submenu:[
            {
                role:'reload'
            },
            {
                label: 'Toggle DevTools',
                accelerator: process.platform =='darwin'? 'Commond+I':'Ctrl+I',//快捷键做os版本区分,
                click(item,focusedWindow){
                    //区分点击窗口
                    focusedWindow.toggleDevTools();
                }
            }
        ]
    });
}

主进程设置好ipc通信口。
监控渲染进程对应事件

addWindow.html

    <div class="container">
            <form>
                    <div>
                        <label>
                            Enter Item
                        </label>
                        <input type="text" id="item" autofocus>
                    </div>
                    <button class="btn waves-effect waves-light" type="submit">add Item</button>
                    <!-- 将item发送到mainWindow -->
                </form>
    </div>
    
    
        const electron = require('electron');
        const {ipcRenderer} = electron;
        
        const form = document.querySelector('form');
        form.addEventListener('submit',function(e){
            e.preventDefault();
            // console.log(123);
            const item = document.querySelector('#item');
            ipcRenderer.send('item:add',item.value);
            //使用ipc进行通信
            
        })

主进程渲染页面

   <nav>
        <div class="nav-wrapper">
            <a class="brand-logo center">
                TODO LIST
            </a>
        </div>
    </nav>
    <ul id="mainContainer">

    </ul>


    <script>
        const electron = require('electron');
        const {ipcRenderer} = electron;
        const ul = document.querySelector('#mainContainer');
        ipcRenderer.on('item:add',function(e,item){
            ul.className = 'collection';
            const li = document.createElement('li');
            li.className = 'collection-item';
            const itemText = document.createTextNode(item);
            li.appendChild(itemText);
            // const li = '<li class="collection-item">'+item+'</li>';
            ul.appendChild(li);  
            // ul.innerHTML = ul.innerHTML+li;     
        })
        //clear items
        ipcRenderer.on('item:clear',function(e){
            ul.innerHTML = '';
            // if(ul.clildren.length == 0){
                ul.className = '';
            // }
        })
        // Remove item by doubleclick
        ul.addEventListener('dblclick',function(e){
            e.target.remove();
            // console.log(ul.clildren.length);
            if(ul.children.length == 0){
                ul.className = '';
            }
        })
    </script>

demo总结

大概可以了解

  • 可以使用node模块
  • 渲染界面可以使用web页面渲染
  • 了解进程间的通信

调试

渲染进程的调试

渲染进行的调试可以通过webContent的openDevTool api打开,调试web页面

const { BrowserWindow } = require('electron')
  
let win = new BrowserWindow()
win.webContents.openDevTools()

主进程调试

命令行开关
--inspect=[port]
监听 V8 引擎中有关 port 的调试器协议信息

--inspect-brk=[port]
和--inspector 一样,但是会在JavaScript 脚本的第一行暂停运行。
外部调试器
  • 通过访问 chrome://inspect 来连接 Chrome 并在那里选择需要检查的Electron 应用程序。
  • 使用 VSCode 进行主进程调试

(感觉这里有点想传统的debug nodejs in browser)


chromium

因为是使用chromium结合,所以我们同时也可以使用HTML5新特性在我们渲染进程里
例如离线通知,离线缓存等。


总结

  • Electron 是一个可以用 JavaScript、HTML 和 CSS 构建桌面应用程序的库
  • 可以多端兼容(一套代码)
  • Chromium、Node.js 、调用操作系统本地功能的 API
  • 无缝使用Node

一些链接


在这些地方可以找到我

题外话

本文写成较早,有些不妥之处还望谅解(2017年)

如有疑惑欢迎讨论。


ZWkang
594 声望18 粉丝

所有的flag都完不成的。